import java.util.Observable;
import java.util.Scanner;

/**
 * Class representing a console based user interface.
 * Responsible for inputting the player's guesses for each square
 * and for displaying the board.
 * @author Kat.Winter
 */
public class ConsoleUI implements SudokuUI {
	
	private SudokuGame game;
	/**
	 * Size of this Sudoku grid
	 */
	private int size;
	/**
	 * Should the game continue or not, true or false
	 */
	private boolean gameContinue = true;
	/**
	 * Row of the desired square
	 */
	private int row;
	/**
	 * Column of the desired square
	 */
	private int col;
	/**
	 * Integer that the player wants filled into given square
	 */
	private int guess;
	/**
	 * Number the player needs to input to call the quit() method
	 */
	private final int quitNum = 99;
	/**
	 * Number the player needs to input to call the reset() method
	 */
	private final int resetNum = 55;
	/**
	 * Scanner input object to read console input
	 */
	Scanner keyboard = new Scanner(System.in);
	
	/**
	 * Constructor - assigns the passed in game to the
	 * local variable for SudokuGame game.
	 * @param game  This instance of SudokuGame
	 */
	public ConsoleUI(SudokuGame game, int size) {
		
		this.game = game;
		this.size = size;
	}
	
	/**
	 * Tells the Game to reset and also resets the User Interface
	 */
	private void reset() {
		
		game.resetBoard();
		startUI();
	}
	
	/**
	 * Draws the initial board and then starts the game.
	 */
	public void startUI() {
		
		gameContinue = true;
		showBoard();	
		playGame();
	}
	
	public void newGame() {
		
		game.newGame();
		startUI();
	}
	
	/**
	 * Displays the Sudoku board
	 */
	private void showBoard() {
		
		System.out.print("    ");
		
		for (int top = 1; top <= size; top++) {
			
			System.out.print(top + " ");
			
			if ((top % Math.sqrt(size)) == 0) {
				
				System.out.print("  ");
			}
		}
		
		drawBar();
		
		for (int row = 1; row <= size; row++) {
			
			System.out.print("\n" + row + " | ");
			
			for (int col = 1; col <= size; col++) {
				
				System.out.print(game.getSquare(row - 1, col - 1) + " ");
				
				if ((col % Math.sqrt(size)) == 0) {
					
					System.out.print("| ");
				}
			}
			
			if ((row % Math.sqrt(size)) == 0) {
				
				drawBar();
			}
		}	
	}
	
	/**
	 * Displays the horizontal bar dividing each row of sections
	 */
	private void drawBar() {
		
		System.out.print("\n    ");
		
		for (int bar = 1; bar <= size; bar++) {
			
			System.out.print("- ");
			
			if ((bar % Math.sqrt(size)) == 0) {
				
				System.out.print("  ");
			}
		}
	}
	
	/**
	 * Displays a message to notify the player they have won.
	 */
	private void showWin() {
		
		System.out.println("\nCongratulations! You have beat the game!");
	}
	
	/**
	 * Beings playing the game
	 */
	private void playGame() {
		
		do {
			
			inputRowsCols();
			
			inputGuess();
			
			game.processGuess(row, col, guess);
			
		} while (gameContinue);
	}
	
	/**
	 * Exits the game unless the player chooses to play again
	 */
	private void quit() {

		if (playAgain()) {
			
			newGame();
		}
		
		else {
			
			System.exit(0);
		}
	}
	
	/**
	 * Inputs row and column selection from the player
	 * and displays an error if there is an error with the input
	 */
	public void inputRowsCols() {
		
		boolean hasError;
		
		do {
			
			hasError = false;
			
			System.out.print("\nEnter 99 at any time to quit or 55 to start over.");
			System.out.print("\nSelect a square.");
			System.out.print("\nEnter row:\n-->");
			row = keyboard.nextInt() - 1;
			
			if (row == quitNum - 1) {
				
				quit();
			}
			
			if (row == resetNum - 1) {
				
				reset();
			}
			
			System.out.print("\nEnter column:\n-->");
			col = keyboard.nextInt() - 1;
			
			if (col == quitNum - 1){
				
				quit();
			}
			
			if (col == resetNum - 1) {
				
				reset();
			}
			
			game.validateInput(row, col);
			if (game.hasError()) {
				
				showBoard();
				System.out.println("\nError: \n" + game.getError());
				hasError = true;
			}
			
		} while (hasError);
	}
	
	/**
	 * Inputs a number value for a given square from the player
	 * and displays an error if there is an error with the input
	 */
	public void inputGuess() {
		
		boolean hasError;
		
		do {
			
			hasError = false;
			
			System.out.println(game.getPossible(row, col));
			
			System.out.print("\nEnter 99 at any time to quit or 55 to start over.");
			System.out.print("\nEnter your value for this square (0 to reset the square):\n-->");
			guess = keyboard.nextInt();
			
			if (guess == quitNum){
				
				quit();
			}
			
			if (guess == resetNum) {
				
				reset();
			}
			
			game.validateGuess(row, col, guess);
			
			if (game.hasError()) {
				
				showBoard();
				System.out.println("\nError: \n" + game.getError());
				hasError = true;
			}
			
		} while (hasError);
	}
	
	/**
	 * When prompted by the Observable object, in this case the SudokuGame,
	 * displays the current state of the board, checks the status of the game - 
	 * won or lost, and responds accordingly by either continuing the game
	 * or by ending it.
	 */
	public void update(Observable ob, Object obj) {
		
		checkGameStatus();
		showBoard();
		playGame();
	}
	
	/**
	 * Checks to see if the game is won.
	 * If the game is won, it sends a congratulation message and if the player
	 * wants to play again, it resets the gameboard.
	 */
	public void checkGameStatus() {
		
		if (game.isGameWon()) {
			
			showWin();
			
			gameContinue = false;
			
			if (playAgain()) {
				
				newGame();
			}
		}	
	}
	
	/**
	 * Inputs whether or not the player would like to play again.
	 * @return  True if the player wants to play again, False if the player does not
	 */
	private boolean playAgain() {
		
		char playAgain;
		
		boolean invalid = false;
		
		do {
			
			System.out.print("\nWould you like to play again? Y or N\n-->");
			
			playAgain = Character.toUpperCase(keyboard.next().charAt(0));

			if (playAgain != 'Y' && playAgain != 'N') {
				
				System.out.print("\nError:\nInvalid input. Please enter Y or N.");
				invalid = true;
			}
			
		} while (invalid);
		
		if (playAgain == 'Y') {
			
			return true;
		}
		
		else {
			
			return false;
		}
	}
}
